Leaflet Blog in Deno Fresh
1/** @jsxImportSource preact */ 2import { CSS, render } from "@deno/gfm"; 3import { Handlers, PageProps } from "$fresh/server.ts"; 4 5import { Layout } from "../../islands/layout.tsx"; 6import { PostInfo } from "../../components/post-info.tsx"; 7import { Title } from "../../components/typography.tsx"; 8import { getPost } from "../../lib/api.ts"; 9import { Head } from "$fresh/runtime.ts"; 10 11interface Post { 12 uri: string; 13 value: { 14 title: string; 15 content: string; 16 createdAt: string; 17 }; 18} 19 20// Only override backgrounds in dark mode to make them transparent 21const transparentDarkModeCSS = ` 22@media (prefers-color-scheme: dark) { 23 .markdown-body { 24 color: white; 25 background-color: transparent; 26 } 27 28 .markdown-body a { 29 color: #58a6ff; 30 } 31 32 .markdown-body blockquote { 33 border-left-color: #30363d; 34 background-color: transparent; 35 } 36 37 .markdown-body pre, 38 .markdown-body code { 39 background-color: transparent; 40 color: #c9d1d9; 41 } 42 43 .markdown-body table td, 44 .markdown-body table th { 45 border-color: #30363d; 46 background-color: transparent; 47 } 48} 49 50.font-sans { font-family: var(--font-sans); } 51.font-serif { font-family: var(--font-serif); } 52.font-mono { font-family: var(--font-mono); } 53 54.markdown-body h1 { 55 font-family: var(--font-serif); 56 text-transform: uppercase; 57 font-size: 2.25rem; 58} 59 60.markdown-body h2 { 61 font-family: var(--font-serif); 62 text-transform: uppercase; 63 font-size: 1.75rem; 64} 65 66.markdown-body h3 { 67 font-family: var(--font-serif); 68 text-transform: uppercase; 69 font-size: 1.5rem; 70} 71 72.markdown-body h4 { 73 font-family: var(--font-serif); 74 text-transform: uppercase; 75 font-size: 1.25rem; 76} 77 78.markdown-body h5 { 79 font-family: var(--font-serif); 80 text-transform: uppercase; 81 font-size: 1rem; 82} 83 84.markdown-body h6 { 85 font-family: var(--font-serif); 86 text-transform: uppercase; 87 font-size: 0.875rem; 88} 89`; 90 91export const handler: Handlers<Post> = { 92 async GET(_req, ctx) { 93 try { 94 const { slug } = ctx.params; 95 const post = await getPost(slug); 96 return ctx.render(post); 97 } catch (error) { 98 console.error("Error fetching post:", error); 99 return new Response("Post not found", { status: 404 }); 100 } 101 }, 102}; 103 104export default function BlogPage({ data: post }: PageProps<Post>) { 105 if (!post) { 106 return <div>Post not found</div>; 107 } 108 109 return ( 110 <> 111 <Head> 112 <title>{post.value.title} knotbin</title> 113 <meta name="description" content="by Roscoe Rubin-Rottenberg" /> 114 {/* Merge GFM's default styles with our dark-mode overrides */} 115 <style 116 dangerouslySetInnerHTML={{ __html: CSS + transparentDarkModeCSS }} 117 /> 118 </Head> 119 120 <Layout> 121 <div class="p-8 pb-20 gap-16 sm:p-20"> 122 <link rel="alternate" href={post.uri} /> 123 <div class="max-w-[600px] mx-auto"> 124 <article class="w-full space-y-8"> 125 <div class="space-y-4 w-full"> 126 <Title>{post.value.title}</Title> 127 <PostInfo 128 content={post.value.content} 129 createdAt={post.value.createdAt} 130 includeAuthor 131 className="text-sm" 132 /> 133 <div class="diagonal-pattern w-full h-3" /> 134 </div> 135 <div class="[&>.bluesky-embed]:mt-8 [&>.bluesky-embed]:mb-0"> 136 <div 137 class="mt-8 markdown-body" 138 // replace old pds url with new one for blob urls 139 dangerouslySetInnerHTML={{ 140 __html: render(post.value.content).replace( 141 /puffball\.us-east\.host\.bsky\.network/g, 142 "knotbin.xyz", 143 ), 144 }} 145 /> 146 </div> 147 </article> 148 </div> 149 </div> 150 </Layout> 151 </> 152 ); 153}